﻿using Cms.Core.Data.Audit;
using Cms.Core.Data.Mapping;
using Cms.Core.Data.Model;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Cms.Core.Data.UnitOfWork
{
    /// <summary>
    /// 上下文基类
    /// </summary>
    public class DbContextBase : DbContext
    {

        public DbContextBase(DbContextOptions options)
         : base(options)
        {

        }




        public override int SaveChanges()
        {

            return base.SaveChanges();
        }


        /// <summary>
        /// 重写保存异步方法
        /// </summary>
        /// <param name="acceptAllChangesOnSuccess"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>

        public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
        {
            return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
        }

        /// <summary>
        /// 重写保存异步方法
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            var auditEntries = OnBeforeSaveChanges();
            var result = await base.SaveChangesAsync(cancellationToken);
            await OnAfterSaveChanges(auditEntries);
            return result;
        }

        /// <summary>
        /// 检查是否要启动审计
        /// </summary>
        /// <param name="entityEntry"></param>
        /// <returns></returns>
        private bool CheckAuditEntry(EntityEntry entityEntry)
        {
            if (entityEntry.Entity is IAuditEntry auditEntry)
            {
                if (auditEntry.IsAutoAuditEntry == true)
                {
                    return true;
                }
            }
            return false;
        }
        private List<AuditEntityEntry> OnBeforeSaveChanges()
        {
            ChangeTracker.DetectChanges();
            var auditEntries = new List<AuditEntityEntry>();
            EntityState[] states = { EntityState.Added, EntityState.Modified, EntityState.Deleted };
            foreach (var entry in ChangeTracker.Entries().Where(s=>states.Contains(s.State)&&(CheckAuditEntry(s))))
            {
                //if (entry.Entity is AuditEntry || entry.State == EntityState.Detached || entry.State == EntityState.Unchanged)
                //    continue;

                var auditEntry = new AuditEntityEntry(entry);
                auditEntry.TableName = entry.Metadata.Relational().TableName;
                auditEntries.Add(auditEntry);

                foreach (var property in entry.Properties)
                {
                    if (property.IsTemporary)
                    {
                    
                        auditEntry.TemporaryProperties.Add(property);
                        continue;
                    }

                    string propertyName = property.Metadata.Name;
                    if (property.Metadata.IsPrimaryKey())
                    {
                        auditEntry.KeyValues[propertyName] = property.CurrentValue;
                        continue;
                    }

                    switch (entry.State)
                    {
                        case EntityState.Added:
                            auditEntry.NewValues[propertyName] = property.CurrentValue;
                            break;

                        case EntityState.Deleted:
                            auditEntry.OldValues[propertyName] = property.OriginalValue;
                            break;

                        case EntityState.Modified:
                            if (property.IsModified)
                            {
                                auditEntry.OldValues[propertyName] = property.OriginalValue;
                                auditEntry.NewValues[propertyName] = property.CurrentValue;
                            }
                            break;
                    }
                }
            }

        
            foreach (var auditEntry in auditEntries.Where(_ => !_.HasTemporaryProperties))
            {
                base.Add(auditEntry.ToAudit());
            }

          
            return auditEntries.Where(_ => _.HasTemporaryProperties).ToList();
        }

        private Task OnAfterSaveChanges(List<AuditEntityEntry> auditEntries)
        {
            if (auditEntries == null || auditEntries.Count == 0)
                return Task.CompletedTask;


            foreach (var auditEntry in auditEntries)
            {
            
                foreach (var prop in auditEntry.TemporaryProperties)
                {
                    if (prop.Metadata.IsPrimaryKey())
                    {
                        auditEntry.KeyValues[prop.Metadata.Name] = prop.CurrentValue;
                    }
                    else
                    {
                        auditEntry.NewValues[prop.Metadata.Name] = prop.CurrentValue;
                    }
                }

                base.Add(auditEntry.ToAudit());
            }

            return SaveChangesAsync();
        }

    }
}
